/**

Copyright (C) SYSTAP, LLC DBA Blazegraph 2006-2016.  All rights reserved.

Contact:
     SYSTAP, LLC DBA Blazegraph
     2501 Calvert ST NW #106
     Washington, DC 20008
     licenses@blazegraph.com

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/
/*
 * Created on Aug 25, 2011
 */

package com.bigdata.bop;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.Arrays;
import java.util.Iterator;
import java.util.Map;

import com.bigdata.btree.Tuple;
import com.bigdata.rdf.sparql.ast.IValueExpressionNode;

/**
 * Base class with some common methods for mutable and copy-on-write {@link BOp}
 * s.
 * 
 * @author <a href="mailto:thompsonbry@users.sourceforge.net">Bryan Thompson</a>
 * @version $Id$
 */
abstract public class CoreBaseBOp implements BOp {

    /**
     * 
     */
    private static final long serialVersionUID = 1L;

    /**
     * The default initial capacity used for an empty annotation map -- empty
     * maps use the minimum initial capacity to avoid waste since we create a
     * large number of {@link BOp}s during query evaluation.
     */
    static protected transient final int DEFAULT_INITIAL_CAPACITY = 2;

    /**
     * Check the operator argument.
     * 
     * @param args
     *            The arguments.
     * 
     * @throws IllegalArgumentException
     *             if the arguments are not valid for the operator.
     */
    protected void checkArgs(final BOp[] args) {

    }

    /**
     * Deep copy clone semantics.
     * <p>
     * {@inheritDoc}
     */
    @Override
    public CoreBaseBOp clone() {
        final Class<? extends CoreBaseBOp> cls = getClass();
        final Constructor<? extends CoreBaseBOp> ctor;
        try {
            ctor = cls.getConstructor(new Class[] { cls });
            return ctor.newInstance(new Object[] { this });
        } catch (SecurityException e) {
            throw new RuntimeException(e);
        } catch (NoSuchMethodException e) {
            throw new RuntimeException(e);
        } catch (InstantiationException e) {
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        } catch (InvocationTargetException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * General contract is a short (non-recursive) representation of the
     * {@link BOp}.
     */
    @Override
    public String toShortString() {
        final BOp t = this;
        if (t instanceof IValueExpression<?>
                || t instanceof IValueExpressionNode) {
            /*
             * Note: toShortString() is intercepted for a few bops, mainly those
             * with a pretty simple structure. This delegates to toString() in
             * those cases.
             */
            return t.toString();
        } else {
            final StringBuilder sb = new StringBuilder();
            sb.append(t.getClass().getSimpleName());
            final Integer tid = (Integer) t.getProperty(Annotations.BOP_ID);
            if (tid != null) {
                sb.append("[" + tid + "]");
//            } else {
//                sb.append("@"+t.hashCode());
            }
            return sb.toString();
        }
    }
    
    /**
     * Return a non-recursive representation of the arguments and annotations
     * for this {@link BOp}.
     */
    @Override
    public String toString() {
        
        final StringBuilder sb = new StringBuilder();
        sb.append(getClass().getName());
//        sb.append(super.toString());
        final Integer bopId = (Integer) getProperty(Annotations.BOP_ID);
        if (bopId != null) {
            sb.append("[" + bopId + "]");
        }
        sb.append("(");
        int nwritten = 0;
        final Iterator<BOp> itr = argIterator();
        while(itr.hasNext()) {
            final BOp t = itr.next();
            if (nwritten > 0)
                sb.append(',');
            if (t == null) {
                sb.append("<null>");
            } else {
                sb.append(t.toShortString());
            }
            nwritten++;
        }
        sb.append(")");
        annotationsToString(sb);
        return sb.toString();

    }
    
    /**
     * Append a name to a string buffer, possibly shortening the name.
     * The current algorithm for name shortening is to take the end of the name
     * after the pen-ultimate '.'.
     * @param sb
     * @param longishName
     */
    protected void shortenName(final StringBuilder sb, final String longishName) {
    	int lastDot = longishName.lastIndexOf('.');
    	if (lastDot != -1) {
    		int lastButOneDot = longishName.lastIndexOf('.', lastDot - 1);
    		sb.append(longishName.substring(lastButOneDot + 1));
    		return;
    	}
    	sb.append(longishName);
    }

    /**
     * Add a string representation of annotations into a string builder.
     * By default this is a non-recursive operation, however
     * subclasses may override {@link #annotationValueToString(StringBuilder, BOp, int)}
     * in order to make this recursive.
     * @param sb
     */
    protected void annotationsToString(final StringBuilder sb) {
        annotationsToString(sb, 0);
    }

    /**
     * Add a string representation of annotations into a string builder.
     * By default this is a non-recursive operation, however
     * subclasses may override {@link #annotationValueToString(StringBuilder, BOp, int)}
     * in order to make this recursive.
     * @param sb
     */
	protected void annotationsToString(final StringBuilder sb, final int indent) {
		final Map<String,Object> annotations = annotations();
        if (!annotations.isEmpty()) {
            sb.append("[");
            boolean first = true;
            for (Map.Entry<String, Object> e : annotations.entrySet()) {
                if (first)
                    sb.append(" ");
                else
                    sb.append(", ");
                final String key = e.getKey();
                final Object val = e.getValue();
                shortenName(sb, key);
                sb.append("=");
                if (val != null && val.getClass().isArray()) {
                    sb.append(Arrays.toString((Object[]) val));
                } else if (key.equals(IPredicate.Annotations.FLAGS)) {
                    sb.append(Tuple.flagString((Integer) val));
                } else if( val instanceof BOp) {
                    annotationValueToString(sb, (BOp)val, indent);
                } else {
                    sb.append(val);
                }
                first = false;
            }
            sb.append("]");
        }
	}

    /**
     * Add a string representation of a BOp annotation value into a string builder.
     * By default this is a non-recursive operation, however
     * subclasses may override and give a recursive definition, which should respect
     * the given indent.
     * @param sb The destination buffer
     * @param val The BOp to serialize
     * @param indent An indent to use if a recursive approach is chosen.
     */
	protected void annotationValueToString(final StringBuilder sb, final BOp val, final int indent) {
		sb.append(val.toString());
	}
    
    @Override
    final public Object getRequiredProperty(final String name) {

        final Object tmp = getProperty(name);

        if (tmp == null)
            throw new IllegalStateException("Required property: " + name
                    + " : " + this.getClass());

        return tmp;
        
    }

    @Override
    @SuppressWarnings("unchecked")
    final public <T> T getProperty(final String name, final T defaultValue) {

        final Object val = getProperty(name);

        if (val == null)
            return defaultValue;

        if (defaultValue != null && val.getClass() != defaultValue.getClass()) {

            /*
             * Attempt to convert to the correct target type.
             */
            
            if (defaultValue.getClass() == Integer.class) {
                return (T) Integer.valueOf("" + val);
            }
            if (defaultValue.getClass() == Long.class) {
                return (T) Long.valueOf("" + val);
            }
            if (defaultValue.getClass() == Float.class) {
                return (T) Float.valueOf("" + val);
            }
            if (defaultValue.getClass() == Double.class) {
                return (T) Double.valueOf("" + val);
            }
            if (defaultValue.getClass() == Boolean.class) {
                return (T) Boolean.valueOf("" + val);
            }

        }

        return (T) val;

    }

    @Override
    final public int getId() {
        
        return (Integer) getRequiredProperty(Annotations.BOP_ID);
        
    }

    @Override
    final public boolean isController() {

        return getProperty(Annotations.CONTROLLER,
                Annotations.DEFAULT_CONTROLLER);

    }
    
    @Override
    final public BOpEvaluationContext getEvaluationContext() {

        return getProperty(Annotations.EVALUATION_CONTEXT,
                Annotations.DEFAULT_EVALUATION_CONTEXT);

    }

    /**
     * <code>true</code> if all arguments and annotations are the same.
     */
    @Override
    public boolean equals(final Object other) {

        if (this == other)
            return true;

        if (!(other instanceof BOp))
            return false;
        
        if(this.getClass() != other.getClass())
            return false;

        final BOp o = (BOp) other;

        final int arity = arity();

        if (arity != o.arity())
            return false;

        for (int i = 0; i < arity; i++) {

            final BOp x = get(i);

            final BOp y = o.get(i);

            /*
             * X Y same same : continue (includes null == null); null other :
             * return false; !null other : if(!x.equals(y)) return false.
             */
            if (x != y && x != null && !(x.equals(y))) {

                return false;

            }

        }

        return annotationsEqual(o);

    }

    /**
     * Return <code>true</code> iff the annotations of this {@link BOp} and the
     * other {@link BOp} are equals.
     * <p>
     * Note: This method permits override by subclasses with direct access to
     * the maps to be compared.
     * 
     * @see #annotationsEqual(Map, Map)
     */
    protected boolean annotationsEqual(final BOp o) {

        final Map<String, Object> m1 = annotations();
        
        final Map<String, Object> m2 = o.annotations();
        
        return annotationsEqual(m1,m2);
        
    }    
    
    /**
     * Compares two maps. If the value under a key is an array, then uses
     * {@link Arrays#equals(Object[], Object[])} to compare the values rather
     * than {@link Object#equals(Object)}. Without this, two bops having array
     * annotation values which have the same data but different array instances
     * will not compare as equal.
     * 
     * @param m1
     *            One set of annotations.
     * @param m2
     *            Another set of annotations.
     *            
     * @return <code>true</code> iff the annotations have the same data.
     */
    static protected final boolean annotationsEqual(
            final Map<String, Object> m1,//
            final Map<String, Object> m2//
            ) {
        
        if (m1 == m2)
            return true;

        if (m1 != null && m2 == null)
            return false;

        if (m1.size() != m2.size())
            return false;
        
        final Iterator<Map.Entry<String,Object>> itr = m1.entrySet().iterator();
        
        while(itr.hasNext()) {

            final Map.Entry<String,Object> e = itr.next();
            
            final String name = e.getKey();
            
            final Object v1 = e.getValue();
            
            final Object v2 = m2.get(name);
            
            if(v1 == v2)
                continue;

            if (v1 == null || v2 == null)
                return false;

            if (v1.getClass().isArray()) {
                // Arrays.equals(v1,v2).
                if (!v2.getClass().isArray())
                    return false;
                if (!Arrays.equals((Object[]) v1, (Object[]) v2)) {
                    return false;
                }
            } else {
                // Object.equals().
                if(!v1.equals(v2))
                    return false;
            }
            
        }
        
        return true;
        
    }

    /**
     * The hash code is based on the hash of the operands (cached).
     */
    @Override
    public int hashCode() {

        int h = hash;

        if (h == 0) {

            final int n = arity();

            for (int i = 0; i < n; i++) {

                final BOp arg = get(i);
                
                h = 31 * h + (arg == null ? 0 : arg.hashCode());

            }

            hash = h;

        }

        return h;

    }

    /**
     * Caches the hash code.
     */
    private int hash = 0;

    /**
     * Returns a string that may be used to indent a dump of the nodes in the
     * tree.
     * <p>
     * Note: The string is capped out after a maximum supported depth.
     * 
     * @param depth
     *            The indentation depth.
     * 
     * @return A string suitable for indent at that height.
     */
    public static String indent(final int depth) {

        if (depth < 0) {

            return "";

        }

        return ws.substring(0, Math.min(ws.length(), depth * 2));

    }

    /**
     * The contract of this method at this level is under-specified.
     * Sub-classes may choose between:
     * 
     * - return a string representation of the object, similar to the use of {@link #toString()}
     * 
     * Or:
     * 
     * - return a pretty-print representation of the object with indent
     * 
     * Note that the former contract may or may not include recursive descent through a tree-like
     * object, whereas the latter almost certainly does.
     * 
     * @param indent
     * @return
     */
	public String toString(int indent) {
		return toString();
	}
	
    private static final transient String ws = "                                                                                                                                                                                                                                                                                                                                                                                                                                    ";

    /**
     * Invoked automatically any time a mutation operation occurs. The default
     * implementation is a NOP.
     */
    protected void mutation() {
        // NOP
    }

}
